﻿using Autofac;
using Autofac.Core;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.DynamicProxy;
using Castle.DynamicProxy;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using NLog;
using NLog.Extensions.Logging;
using NLog.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Umi.Web.Abstraction.Aspect;
using Umi.Web.Filters;
using Umi.Web.Metadatas.Attributes;

namespace Umi.Web
{
    public class Program
    {
        public static IEnumerable<Assembly> LoadedAssemblies { get; private set; }

        public static IEnumerable<object> InitedStartup { get; private set; }

        public static void Main(string[] args)
        {
            NLogBuilder.ConfigureNLog("nlog.config");
            CreateWebHostBuilder(args)
                .Build()
                .Run();
        }

        public static IHostBuilder CreateWebHostBuilder(string[] args)
            => new HostBuilder()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .ConfigureHostConfiguration(config => HostConfiguration(config, args))
            .ConfigureAppConfiguration((context, config) => AppConfiguration(context, config, args))
            .ConfigureContainer<ContainerBuilder>(LoadAssemblies)
            .UseServiceProviderFactory(ServiceProviderFactory)
            .ConfigureLogging(LoggerConfigure)
            .ConfigureWebHostDefaults(WebHostConfigure)
            .ConfigureServices(ConfigureServices)
            .UseConsoleLifetime()
            .UseNLog();

        public static void HostConfiguration(IConfigurationBuilder config, string[] args)
        {
            config.AddEnvironmentVariables(); // TODO: Prefix?
            if (args != null)
            {
                config.AddCommandLine(args);
            }
        }

        private static void LoggerConfigure(HostBuilderContext context, ILoggingBuilder builder)
        {
            builder.AddConfiguration(context.Configuration.GetSection("Logging"));

            builder.ClearProviders()
                .AddNLog();
        }

        public static IServiceProviderFactory<ContainerBuilder> ServiceProviderFactory(HostBuilderContext context)
        {
            return new AutofacServiceProviderFactory();
        }


        public static void AppConfiguration(HostBuilderContext context, IConfigurationBuilder config, string[] args)
        {
            var env = context.HostingEnvironment;
            config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                  .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

            if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
            {
                var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
                if (appAssembly != null)
                {
                    config.AddUserSecrets(appAssembly, optional: true);
                }
            }

            config.AddEnvironmentVariables();

            if (args != null)
            {
                config.AddCommandLine(args);
            }
        }

        public static void LoadAssemblies(HostBuilderContext context, ContainerBuilder builder)
        {
            builder.RegisterAssemblyModules(typeof(Program).Assembly);
            builder.RegisterType<TimeLoggerInterceptor>()
                .As<IInterceptor>()
                .Named("TimeLoggerInterceptor", typeof(IInterceptor))
                .PropertiesAutowired();
            builder.RegisterType<ExceptionInterceptor>()
                .As<IInterceptor>()
                .Named("ExceptionInterceptor", typeof(IInterceptor))
                .PropertiesAutowired();

            foreach (var item in LoadedAssemblies)
            {
                builder.RegisterAssemblyModules(item);
                RegisterTypes(builder, item);
            }

        }

        private static void RegisterTypes(ContainerBuilder builder, Assembly assembly)
        {
            foreach (var e in assembly.GetTypes())
            {
                IEnumerable<ServiceAttribute> services = e.GetCustomAttributes<ServiceAttribute>();
                if (!services.Any())
                {
                    continue;
                }
                ServiceAttribute service = services.First();
                List<string> list = new List<string>
                    {
                        "TimeLoggerInterceptor",
                        "ExceptionInterceptor"
                    };
                string[] interceptors = service.Interceptors ?? new string[0];
                list.AddRange(interceptors);
                var register = builder.RegisterType(e)
                    .AsImplementedInterfaces();
                switch (service.Scoped)
                {
                    case ServiceScoped.Dependency:
                        register = register.InstancePerDependency();
                        break;
                    case ServiceScoped.Request:
                        register = register.InstancePerLifetimeScope();
                        break;
                    case ServiceScoped.Single:
                        register = register.SingleInstance();
                        break;
                    default:
                        register = register.InstancePerLifetimeScope();
                        break;
                }
                if (!string.IsNullOrEmpty(service.Name))
                {
                    foreach (var item in e.GetInterfaces())
                    {
                        if (!item.IsPublic)
                            continue;
                        register = register.Named(service.Name, item);
                    }
                }
                register = register.PropertiesAutowired(new DefaultPropertySelector(false), true);
                if (service.IsAsSelf)
                {
                    register = register.AsSelf()
                        .EnableClassInterceptors()
                        .InterceptedBy(list.ToArray());
                }
                else
                {
                    register = register.EnableInterfaceInterceptors()
                                    .InterceptedBy(list.ToArray());
                }
            }
        }

        public static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
        {

            services.AddRouting();
            //注册跨域资源共享
            // services.AddCors();
            services.AddSingleton<ExceptionFilter>();
            //加入日志
            services.AddLogging();

            //注册配置文件字段
            services.AddOptions();

            // 注册HttpContext访问器
            services.AddHttpContextAccessor();
            IServiceProvider provider = services.BuildServiceProvider();

            Microsoft.Extensions.Logging.ILogger logger = provider.GetRequiredService<ILogger<Program>>();

            //替换Controller为服务实现,必须放在 services.AddMvc() 之前
            services.Replace(ServiceDescriptor.Scoped<IControllerActivator, ServiceBasedControllerActivator>());
            var mvc = services.AddControllersWithViews(mvc => mvc.Filters.AddService<ExceptionFilter>())
                         .AddNewtonsoftJson(option =>
                         {
                             option.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
                             option.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
                             option.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                             option.SerializerSettings.DateParseHandling = DateParseHandling.DateTime;
                             option.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                         });

            List<object> startupObj = new List<object>();
            InitedStartup = startupObj;
            IConfiguration configuration = context.Configuration.GetSection("AutofacLoadPath");
            logger.LogDebug("开始扫描配制文件中给出的路径");
            string rootPath = context.HostingEnvironment.ContentRootPath;
            List<Assembly> assemblies = new List<Assembly>();
            LoadedAssemblies = assemblies;
            foreach (var cfgPath in configuration.GetChildren())
            {
                string dicPath = Path.Combine(rootPath, cfgPath.Value);
                DirectoryInfo dic = new DirectoryInfo(dicPath);
                logger.LogDebug($"正在扫描 {dic.FullName} 目录");
                if (!dic.Exists)
                {
                    logger.LogWarning($"目录 {dic.FullName} 不存在");
                    continue;
                }
                foreach (var files in dic.GetFiles("*.dll", SearchOption.TopDirectoryOnly))
                {
                    logger.LogDebug($"正在加载 {files.FullName}");
                    try
                    {
                        Assembly assembly = Assembly.LoadFrom(files.FullName);
                        assemblies.Add(assembly);
                        IEnumerable<Type> startUpType = from p in assembly.GetTypes()
                                                        where !p.IsAbstract &&
                                                        !p.IsInterface &&
                                                        p.GetCustomAttribute(typeof(StartupAttribute)) != null
                                                        select p;
                        mvc.AddApplicationPart(assembly);
                        foreach (Type jitem in startUpType)
                        {
                            logger.LogDebug($"找到Startup类 {jitem.FullName}");
                            ObjectFactory factory = ActivatorUtilities.CreateFactory(jitem, Type.EmptyTypes);
                            object obj = factory(provider, null);
                            startupObj.Add(obj);
                            MethodInfo method = jitem.GetMethod("ConfigureService");
                            if (method == null)
                                continue;
                            ParameterInfo[] parameters = method.GetParameters();
                            object[] args = GetParametersObj(parameters, provider);
                            for (int i = 0; i < parameters.Length; i++)
                            {
                                if (parameters[i].ParameterType == typeof(HostBuilderContext))
                                    args[i] = context;
                                if (parameters[i].ParameterType == typeof(IServiceCollection))
                                    args[i] = services;
                            }
                            method.Invoke(obj, args);
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogWarning(ex, $"{files.FullName} 格式或依赖不正确，加载失败");
                    }
                }
            }
        }

        public static void WebHostConfigure(IWebHostBuilder webHostBuilder)
        {
            webHostBuilder.UseKestrel()
                .UseStartup<Startup>();
        }


        public static object[] GetParametersObj(ParameterInfo[] paramTypes, IServiceProvider provider)
        {
            if (paramTypes == null)
                return null;
            object[] ins = new object[paramTypes.Length];
            for (int i = 0; i < paramTypes.Length; i++)
            {
                ins[i] = provider.GetService(paramTypes[i].ParameterType);
            }
            return ins;
        }
    }
}
